package cn.vic.config;

import cn.vic.config.model.DatabaseElement;
import cn.vic.exception.ApplicationException;
import cn.vic.util.StringUtil;
import org.apache.log4j.Logger;
import org.w3c.dom.*;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.transform.OutputKeys;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import javax.xml.xpath.XPath;
import javax.xml.xpath.XPathConstants;
import javax.xml.xpath.XPathExpressionException;
import javax.xml.xpath.XPathFactory;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Properties;

public class Configuration {

    private static final Logger LOGGER = Logger.getLogger(Configuration.class);

    private static final String CONFIGURATION_FILE = "configuration.xml";
    private static final Integer DEFAULT_OUT_THREAD_NUM = 10;
    private static final Integer DEFAULT_CONNECTION_POOL_MIX = 1;
    private static final Integer DEFAULT_CONNECTION_POOL_MAX = 3;
    private String configurationFile;
    private List<DatabaseElement> connectionHistory;
    private List<String> classPathEntries;
    private String outThreadNum;
    /**
     * 数据库连接池 大小
     */
    private String connectionPoolMix;
    private String connectionPoolMax;

    public Configuration(String classPath) {
        this.configurationFile = classPath + CONFIGURATION_FILE;
        connectionHistory = new ArrayList<DatabaseElement>();
        classPathEntries = new ArrayList<String>();
    }

    public void loadConfiguration() throws ApplicationException {
        try {
            DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
            DocumentBuilder docBuilder = factory.newDocumentBuilder();
            Document doc = docBuilder.parse(configurationFile);

            XPathFactory f = XPathFactory.newInstance();
            XPath path = f.newXPath();

            parseClassPathEntry(doc, path);
            parseConnections(doc, path);

            setOutThreadNum(path.evaluate("/configuration/outThreadNum/text()", doc));
            setConnectionPoolMix(path.evaluate("/configuration/connectionPoolMix/text()", doc));
            setConnectionPoolMax(path.evaluate("/configuration/connectionPoolMax/text()", doc));

        } catch (Exception e) {
            throw new ApplicationException(e);
        }
    }

    private void parseClassPathEntry(Document doc, XPath path) throws XPathExpressionException {
        NodeList classpathEntrys = (NodeList) path.evaluate("configuration/classpath/entry", doc, XPathConstants.NODESET);
        if (classpathEntrys != null) {
            for (int i = 0; i < classpathEntrys.getLength(); i++) {
                String entry = parseElementNodeValue(classpathEntrys.item(i));
                if (StringUtil.isNotEmpty(entry)) {
                    classPathEntries.add(entry);
                }
            }
        }
    }

    private void parseConnections(Document doc, XPath path) throws XPathExpressionException {
        NodeList databaseList = (NodeList) path.evaluate("configuration/connections/database", doc,
                XPathConstants.NODESET);
        if (databaseList != null) {
            for (int i = 0; i < databaseList.getLength(); i++) {
                parseDatabase(databaseList.item(i), path);
            }
        }
    }

    private void parseDatabase(Node node, XPath path) throws XPathExpressionException {
        String name = path.evaluate("@name", node);
        String driverClass = path.evaluate("./driverClass/text()", node);
        String url = path.evaluate("./url/text()", node);
        String username = path.evaluate("./username/text()", node);
        String password = path.evaluate("./password/text()", node);
        String schema = path.evaluate("./schema/text()", node);

        if (StringUtil.isNotEmpty(name) && StringUtil.isNotEmpty(driverClass) && StringUtil.isNotEmpty(url)
                && StringUtil.isNotEmpty(username)) {
            connectionHistory.add(new DatabaseElement(name, driverClass, url, username, password, schema));
        }
    }


    private String parseElementNodeValue(Node node) {
        if (node.getFirstChild() != null) {
            return node.getFirstChild().getNodeValue();
        } else {
            return null;
        }
    }

    public DatabaseElement getHistoryByName(String name) {
        for (int i = 0; i < connectionHistory.size(); i++) {
            if (connectionHistory.get(i).getName().equalsIgnoreCase(name)) {
                return connectionHistory.get(i);
            }
        }
        return null;
    }

    public void addHistory(DatabaseElement database) {
        for (int i = 0; i < connectionHistory.size(); i++) {
            DatabaseElement hisItem = connectionHistory.get(i);
            if (hisItem.getName().equalsIgnoreCase(database.getName())) {
                hisItem.setDriverClass(database.getDriverClass());
                hisItem.setConnectionUrl(database.getConnectionUrl());
                hisItem.setUsername(database.getUsername());
                hisItem.setPassword(database.getPassword());
                hisItem.setSchema(database.getSchema());
                return ;
            }
        }
        connectionHistory.add(database);
    }

    public void removeHistory(DatabaseElement database) {
        removeHistory(database.getName());
    }

    public void removeHistory(String name) {
        Iterator<DatabaseElement> iterator = connectionHistory.iterator();
        while (iterator.hasNext()) {
            DatabaseElement hisItem = iterator.next();
            if (hisItem.getName().equalsIgnoreCase(name)) {
                iterator.remove();
                return;
            }
        }
    }

    private Properties parseAttributes(Node node) {
        Properties attributes = new Properties();
        NamedNodeMap nnm = node.getAttributes();
        for (int i = 0; i < nnm.getLength(); i++) {
            Node attribute = nnm.item(i);
            String value = attribute.getNodeValue();
            attributes.put(attribute.getNodeName(), value);
        }

        return attributes;
    }

    public void save() {
        try {
            DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
            DocumentBuilder docBuilder = dbf.newDocumentBuilder();
            Document doc = docBuilder.newDocument();
            Element root = doc.createElement("configuration");
            doc.appendChild(root);

            Element classpathEle = doc.createElement("classpath");
            root.appendChild(classpathEle);
            for (String s : classPathEntries) {
                Element e = doc.createElement("entry");
                e.appendChild(doc.createTextNode(s));
                classpathEle.appendChild(e);
            }

            Element connectionsEle = doc.createElement("connections");
            root.appendChild(connectionsEle);
            for (DatabaseElement d : connectionHistory) {
                writeDatabase(connectionsEle, d);
            }
            // 生成线程数量
            Element e = doc.createElement("outThreadNum");
            e.appendChild(doc.createTextNode(outThreadNum));
            root.appendChild(e);
            // 数据库连接池
            e = doc.createElement("connectionPoolMix");
            e.appendChild(doc.createTextNode(connectionPoolMix));
            root.appendChild(e);

            e = doc.createElement("connectionPoolMax");
            e.appendChild(doc.createTextNode(connectionPoolMax));
            root.appendChild(e);


            // Write the file
            DOMSource ds = new DOMSource(doc);
            StreamResult sr = new StreamResult(new File(configurationFile));
            Transformer t = TransformerFactory.newInstance().newTransformer();
            t.setOutputProperty(OutputKeys.METHOD, "xml");
            t.setOutputProperty(OutputKeys.ENCODING, "utf-8");
            t.setOutputProperty(OutputKeys.INDENT, "yes");
            t.setOutputProperty(OutputKeys.STANDALONE, "yes");
            t.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", "4");
            t.transform(ds, sr);
        } catch (Exception e) {
            LOGGER.info(e.getMessage(), e);
        }
    }

    private void writeDatabase(Element elem, DatabaseElement db) {
        Element e = elem.getOwnerDocument().createElement("database");
        e.setAttribute("name", db.getName());
        setTextChild(e, "driverClass", db.getDriverClass());
        setTextChild(e, "url", db.getConnectionUrl());
        setTextChild(e, "username", db.getUsername());
        setTextChild(e, "password", db.getPassword());
        setTextChild(e, "schema", db.getSchema());

        elem.appendChild(e);
    }


    /**
     * Convenience function to set the text of a child element.
     */
    private void setTextChild(Element elem, String name, String value) {
        Document doc = elem.getOwnerDocument();
        Element e = doc.createElement(name);
        e.appendChild(doc.createTextNode(value));
        elem.appendChild(e);
    }

    public List<DatabaseElement> getConnectionHistory() {
        return connectionHistory;
    }

    public List<String> getClassPathEntries() {
        return classPathEntries;
    }


    public String getOutThreadNum_() {
        return this.outThreadNum;
    }

    public Integer getOutThreadNum() {
        return defaultNum(this.outThreadNum, DEFAULT_OUT_THREAD_NUM);
    }

    public void setOutThreadNum(String outThreadNum) {
        this.outThreadNum = defaultNumStr(outThreadNum, DEFAULT_OUT_THREAD_NUM);
    }


    public void setClassPathEntries(List<String> classPathEntries) {
        this.classPathEntries = classPathEntries;
    }

    public String getConnectionPoolMix() {
        return this.connectionPoolMix;
    }

    public Integer getConnectionPoolMixNum() {
        return defaultNum(this.connectionPoolMix, DEFAULT_CONNECTION_POOL_MIX);
    }

    public void setConnectionPoolMix(String connectionPoolMix) {
        this.connectionPoolMix = defaultNumStr(connectionPoolMix, DEFAULT_CONNECTION_POOL_MIX);
    }

    public String getConnectionPoolMax() {
        return this.connectionPoolMax;
    }

    public Integer getConnectionPoolMaxNum() {
        return defaultNum(this.connectionPoolMax, DEFAULT_CONNECTION_POOL_MAX);
    }

    public void setConnectionPoolMax(String connectionPoolMax) {
        this.connectionPoolMax = defaultNumStr(connectionPoolMax, DEFAULT_CONNECTION_POOL_MAX);
    }

    /**
     * 如果值 异常 就用默认数字
     *
     * @param outThreadNum
     * @param defaultNum
     * @return
     */
    private String defaultNumStr(String outThreadNum, int defaultNum) {
        return String.valueOf(defaultNum(outThreadNum, defaultNum));
    }

    private Integer defaultNum(String outThreadNum, int defaultNum) {
        try {
            Integer in = Integer.valueOf(outThreadNum);
            if (in < 1) in = defaultNum;
            return in;
        } catch (Exception e) {
            return defaultNum;
        }
    }
}
